#include "pch.hpp"
#include "MainWindow.hpp"
#include "ProjectWizard.hpp"

#include <QtUi/Application.hpp>
#include <engine.core/XmlDomStream.hpp>

using namespace Engine::Edit;




Engine::Edit::Ui::MainWindow::MainWindow()
	: menuDebug(nullptr)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Engine::Edit::Ui::MainWindow::setupUi(QMainWindow* window)
{
	__super::setupUi(window);

	menuView->addAction(projectExplorerDock->toggleViewAction());
	menuView->addAction(errorListDock->toggleViewAction());
	menuView->addAction(outputDock->toggleViewAction());
	menuView->addAction(callstackDock->toggleViewAction());
}
///////////////////////////////////////////////////////////////////////////////////////////////////




MainWindow::MainWindow(QWidget* parent)
	: QMainWindow(parent)

	, m_settings("Taleon-Engine", "engine.edit", this)

	, m_state(State::Idle)

	, m_projectDir(QDir::current())

	, m_outputStream(boost::make_shared<logger::memorystream>(services::provider::ptr()))
	, m_autoCompletion(boost::make_shared<AutoCompletion>())
{
	ui.setupUi(this);

	m_engine = ui.previewWidget->engine();

	//
	// Actions
	//

	// File
	verify(connect(ui.actionNew_Project, SIGNAL(triggered()), SLOT(newProject())));
	verify(connect(ui.actionOpen_Project, SIGNAL(triggered()), SLOT(openProject())));
	verify(connect(ui.actionNew_Script, SIGNAL(triggered()), SLOT(newScript())));
	verify(connect(ui.actionNew_Ui_Definition, SIGNAL(triggered()), SLOT(newUiDefinition())));
	verify(connect(ui.actionClose, SIGNAL(triggered()), SLOT(closeDocument())));
	verify(connect(ui.actionClose_Project, SIGNAL(triggered()), SLOT(closeProject())));
	verify(connect(ui.actionSave, SIGNAL(triggered()), SLOT(save())));
	verify(connect(ui.actionSave_all, SIGNAL(triggered()), SLOT(saveAll())));
	verify(connect(ui.actionQuit, SIGNAL(triggered()), SLOT(exit())));

	// Debug
	verify(connect(ui.actionRun_with_Debugger, SIGNAL(triggered()), SLOT(runWithDebugger())));
	verify(connect(ui.actionStop_Debugger, SIGNAL(triggered()), SLOT(stopDebugger())));

	//
	// Views
	//

	verify(connect(ui.projectExplorer, SIGNAL(doubleClicked(ProjectItem::ptr)), ui.mainArea, SLOT(show(ProjectItem::ptr))));
	verify(connect(ui.mainArea, SIGNAL(activeDocumentChanged(DocumentWindow*)), SLOT(documentWindowChanged(DocumentWindow*))));
	verify(connect(ui.mainArea, SIGNAL(windowAdded(DocumentWindow*)), SLOT(documentWindowOpened(DocumentWindow*))));
	verify(connect(ui.mainArea, SIGNAL(windowRemoved(DocumentWindow*)), SLOT(documentWindowClosed(DocumentWindow*))));

	ui.outputWidget->attach(m_outputStream);

	// Final touch
	restoreSettings();
	changeState(State::Idle);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

MainWindow::~MainWindow()
{}
///////////////////////////////////////////////////////////////////////////////////////////////////




void MainWindow::newProject()
{
	// If the user doesn't want to close the old one, then we don't do anything
	if(!shouldClose())
		return;

	ProjectWizard wizard(this);
	wizard.exec();
	m_project = wizard.project();
	if(!m_project)
		return;

	ui.projectExplorer->setProject(m_project);
	ui.mainArea->setProject(m_project);
	m_autoCompletion->setProject(m_project);

	changeState(State::Editing);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::openProject()
{
	if(!shouldClose())
		return;

	QString fileName = QFileDialog::getOpenFileName(this, tr("Open Project"), QDir::currentPath(), tr("Project Files (*.xml)"));
	if(fileName.isEmpty())
		return;

	m_project = Project::create(fileName, fileName);
	QFile file(fileName);
	verify(file.open(QIODevice::ReadOnly | QIODevice::Text));
	QXmlStreamReader reader(&file);
	XmlDomReaderStream stream(reader);
	m_project->load(stream);

	ui.projectExplorer->setProject(m_project);
	ui.mainArea->setProject(m_project);
	m_autoCompletion->setProject(m_project);

	changeState(State::Editing);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::closeDocument()
{
	auto document = ui.mainArea->active();
	if(!document)
		return;

	ui.mainArea->close(document->item());
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::closeProject()
{
	// If the user doesn't want to close the old one, then we don't do anything
	if(!shouldClose())
		return;

	m_project = Project::ptr();

	ui.projectExplorer->setProject(m_project);
	ui.mainArea->setProject(m_project);
	m_autoCompletion->setProject(m_project);

	changeState(State::Idle);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::newScript()
{
	auto scripts = m_project->scripts();
	auto name    = scripts->freeName("Unnamed%1.as");
	auto script  = Script::create(Script::ptr(), name);

	scripts->add(script);

	setWindowModified(true);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::newUiDefinition()
{
	auto uiDefinitions = m_project->uiDefinitions();
	auto name          = uiDefinitions->freeName("Unnamed%1.xml");
	auto uiDef         = UiDefinition::create(UiDefinition::ptr(), name);

	uiDefinitions->add(uiDef);

	setWindowModified(true);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::save()
{
	auto window = ui.mainArea->active();
	if(window)
	{
		window->save();
		updateUi();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::saveAll()
{
	QString project = QDir::current().absoluteFilePath(m_project->name());
	QString fileName = QFileDialog::getSaveFileName(this, tr("Select project"), project, tr("Taleon-Engine Projects (*.xml)"));
	if(!fileName.isEmpty())
	{
		// Tell each view to store the contents in the project again
		ui.mainArea->saveAll();

		QFile file(fileName);
		verify(file.open(QIODevice::WriteOnly | QIODevice::Text));
		QXmlStreamWriter writer(&file);
		XmlDomWriterStream stream(writer);
		m_project->save(stream);

		setWindowModified(false);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::exit()
{
	if(shouldClose())
	{
		saveSettings();
		QApplication::instance()->quit();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::runWithDebugger()
{
	//
	// Phase 1: Save all opened documents
	//

	saveAll();

	// If the user doesn't want to save the project, then we can't start the debugging process
	if(isWindowModified())
		return;


	//
	// Phase 2: Check if ther are errors (for example a script not compiling)
	// TODO
	//


	//
	// Phase 3: Start the debugger
	//

	m_debugger = boost::make_shared<Debugger>(QFileInfo("Game.exe"), 4000, m_outputStream);
	verify(connect(m_debugger.get(), SIGNAL(stateChanged()), SLOT(debuggerStateChanged()), Qt::QueuedConnection));


	//
	// Phase 4: Inform anyone about the state change
	//

	changeState(State::Debugging);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::continueDebugger()
{
	auto future = m_debugger->continueProcess();

	QApplication::setOverrideCursor(Qt::WaitCursor);
	future.ready().connect([]()
	{
		QApplication::restoreOverrideCursor();
	});
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::pauseDebugger()
{
	auto future = m_debugger->pause();

	QApplication::setOverrideCursor(Qt::WaitCursor);
	future.ready().connect([]()
	{
		QApplication::restoreOverrideCursor();
	});
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::stopDebugger()
{
	m_debugger->stop();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::debuggerStateChanged()
{
	if(!m_debugger)
		return;

	switch(m_debugger->state())
	{
	case Debugger::State::Idle:
		changeState(State::Editing); //< Idle means that the debugger is not running anymore
		break;
	case Debugger::State::Starting:
	case Debugger::State::Attaching:
	case Debugger::State::WaitingForResponse:
		ui.actionContinue_Debugger->setEnabled(false);
		ui.actionPause_Debugger->setEnabled(false);
		ui.actionStop_Debugger->setEnabled(true); //< The debugger can be stopped at any time
		break;
	case Debugger::State::Running:
		ui.actionContinue_Debugger->setEnabled(false);
		ui.actionPause_Debugger->setEnabled(true);
		ui.actionStop_Debugger->setEnabled(true);
		break;
	case Debugger::State::Paused:
		ui.actionContinue_Debugger->setEnabled(true);
		ui.actionPause_Debugger->setEnabled(false);
		ui.actionStop_Debugger->setEnabled(true);
		break;
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void MainWindow::documentWindowOpened(DocumentWindow* window)
{
	// First of all, we add the window's error model to the view
	ui.errorView->add(window->errorModel());
	verify(connect(window, SIGNAL(modified(DocumentWindow*)), this, SLOT(documentWindowChanged(DocumentWindow*))));

	const ScriptEditWindow* script = dynamic_cast<const ScriptEditWindow*>(window);
	if(script)
		m_autoCompletion->scriptWindowOpened(script);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::documentWindowChanged(DocumentWindow* window)
{
	const ScriptEditWindow* script = dynamic_cast<const ScriptEditWindow*>(window);
	if(script)
		m_autoCompletion->scriptWindowChanged(script);

	updateUi();

	// Make sure the item is selected and visible in the project tree (makes life easier)
	ui.projectExplorer->ensureVisible(window->item());
	ui.projectExplorer->select(window->item(), QItemSelectionModel::ClearAndSelect);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::documentWindowClosed(DocumentWindow* window)
{
	const ScriptEditWindow* script = dynamic_cast<const ScriptEditWindow*>(window);
	if(script)
		m_autoCompletion->scriptWindowClosed(script);

	ui.errorView->remove(window->errorModel());
	verify(disconnect(window, SIGNAL(modified(DocumentWindow*)), this, SLOT(documentWindowChanged(DocumentWindow*))));
	updateUi();
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void MainWindow::closeEvent(QCloseEvent* event)
{
	if(shouldClose())
	{
		saveSettings();
		event->accept();
	}
	else
	{
		event->ignore();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void MainWindow::changeState(State::Type state)
{
	m_state = state;

	ui.projectExplorer->changeState(state);
	ui.mainArea->changeState(state);
	ui.errorView->changeState(state);

	switch(m_state)
	{
	case State::Idle:

		setWindowTitle("engine.edit");

		ui.actionNew_Script->setEnabled(false);
		ui.actionNew_Ui_Definition->setEnabled(false);
		ui.actionClose->setEnabled(false);
		ui.actionClose_Project->setEnabled(false);

		ui.actionSave->setEnabled(false);
		ui.actionSave_all->setEnabled(false);
		ui.actionSave_as->setEnabled(false);

		delete ui.menuDebug;
		ui.menuDebug = nullptr;

		break;
	case State::Editing:

		setWindowTitle(QString("%1 [*] - engine.edit").arg(m_project->name()));

		ui.actionNew_Script->setEnabled(true);
		ui.actionNew_Ui_Definition->setEnabled(true);
		ui.actionClose->setEnabled(false);
		ui.actionClose_Project->setEnabled(true);

		ui.actionSave->setEnabled(false);
		ui.actionSave_all->setEnabled(false);
		ui.actionSave_as->setEnabled(false);

		if(!ui.menuDebug)
		{
			ui.menuDebug = new QMenu(this);
			ui.menuDebug->setTitle(tr("Debug"));
			ui.menuDebug->addAction(ui.actionRun_with_Debugger);
			ui.menuDebug->addAction(ui.actionRun_Standalone);
			ui.menuDebug->addAction(ui.actionDetach_Debugger);
			ui.menuDebug->addSeparator();
			ui.menuDebug->addAction(ui.actionContinue_Debugger);
			ui.menuDebug->addAction(ui.actionPause_Debugger);
			ui.menuDebug->addAction(ui.actionDetach_Debugger);
			ui.menuDebug->addAction(ui.actionStop_Debugger);

			ui.menubar->insertMenu(ui.menuHelp->menuAction(), ui.menuDebug);
		}

		ui.actionRun_Standalone->setEnabled(true);
		ui.actionRun_with_Debugger->setEnabled(true);

		ui.actionContinue_Debugger->setEnabled(false);
		ui.actionPause_Debugger->setEnabled(false);
		ui.actionDetach_Debugger->setEnabled(false);
		ui.actionStop_Debugger->setEnabled(false);

		m_debugger.reset();

		break;
	case State::Debugging:

		setWindowTitle(QString("%1 [*] (Running) - engine.edit").arg(m_project->name()));

		ui.actionNew_Script->setEnabled(false);
		ui.actionNew_Ui_Definition->setEnabled(false);
		ui.actionClose->setEnabled(false);
		ui.actionClose_Project->setEnabled(false);

		ui.actionSave->setEnabled(false);
		ui.actionSave_all->setEnabled(false);
		ui.actionSave_as->setEnabled(false);

		ui.actionRun_Standalone->setEnabled(false);
		ui.actionRun_with_Debugger->setEnabled(false);

		ui.actionContinue_Debugger->setEnabled(false);
		ui.actionPause_Debugger->setEnabled(false);
		ui.actionDetach_Debugger->setEnabled(false); //< Disabled until I can detach from a process without killing it
		ui.actionStop_Debugger->setEnabled(true);

		break;
	default:
		throw invalid_value_exception(m_state);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::updateUi()
{
	auto document = ui.mainArea->active();

	switch(m_state)
	{
	case State::Editing:
		ui.actionSave->setEnabled(document ? document->isWindowModified() : false);
		// Intentionally no break, the next is for BOTH
		// Editing & Debugging
	case State::Debugging:
		ui.actionClose->setEnabled(document != nullptr);
		break;
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool MainWindow::shouldClose()
{
	if(m_debugger)
	{
		QMessageBox msgBox(this);
		msgBox.setWindowTitle(tr("engine.edit"));
		msgBox.setText(tr("Do you want to stop debugging?"));
		msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
		msgBox.setDefaultButton(QMessageBox::No);
		msgBox.setIcon(QMessageBox::Warning);
		int ret = msgBox.exec();

		switch(ret)
		{
		case QMessageBox::Yes:
			// The user does want to stop debugging, but there might be other stuff that
			// might need to be checked first, therefore we simply continue
			break;
		case QMessageBox::No:
			// The user doesn't want to stop debugging: Therefore we can stop and return false
			// to indicate that the window cannot be closed
			return false;
		default:
			throw invalid_value_exception(ret);
		}
	}

	if(m_project && (isWindowModified() || ui.mainArea->isWindowModified()))
	{
		QMessageBox msgBox(this);
		msgBox.setWindowTitle(tr("engine.edit"));
		msgBox.setText(tr("The project '%1' has been modified.").arg(m_project->name()));
		msgBox.setInformativeText(tr("Do you want to save your changes?"));
		msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
		msgBox.setDefaultButton(QMessageBox::Save);
		msgBox.setIcon(QMessageBox::Question);
		int ret = msgBox.exec();

		switch (ret)
		{
		case QMessageBox::Save:
			saveAll();
			return true;
		case QMessageBox::Discard:
			// Don't Save was clicked: Close the application without saving
			return true;
		case QMessageBox::Cancel:
			// Cancel was clicked: Don't close the application
			return false;
		default:
			throw invalid_value_exception(ret);
		}
	}
	else
	{
		return true;
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void MainWindow::restoreSettings()
{
	m_settings.beginGroup("mainwindow");
	if(m_settings.contains("state")) restoreState(m_settings.value("state").toByteArray());
	if(m_settings.contains("pos")) move(m_settings.value("pos").toPoint());
	if(m_settings.contains("size")) resize(m_settings.value("size").toSize());
	if(m_settings.contains("maximized") && m_settings.value("maximized").toBool()) showMaximized();

	// TODO: restore state of child items of the mainwindow
	restoreSettings(ui.errorView);

	m_settings.endGroup();

	// TODO: restore state of items unrelated to the mainwindow
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void MainWindow::saveSettings()
{
	m_settings.beginGroup("mainwindow");
	m_settings.setValue("state", saveState());
	m_settings.setValue("pos", pos());
	m_settings.setValue("size", size());
	m_settings.setValue("maximized", isMaximized());

	// TODO: save state of more items that are childs of the mainwindow
	saveSettings(ui.errorView);

	m_settings.endGroup();

	// TODO: save general state of items, unrelated to the mainwindow
}
///////////////////////////////////////////////////////////////////////////////////////////////////
